﻿using GenericRepositoryUnitOfWorkDemo.Domain.Abstracts.Entites;
using GenericRepositoryUnitOfWorkDemo.Domain.Abstracts.Repositories;
using GenericRepositoryUnitOfWorkDemo.Infrastructure.Abstracts.Repositories;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;

namespace GenericRepositoryUnitOfWorkDemo.Api.Extensions
{
    public static class RepositoryExtensions
    {
        public static IServiceCollection AddRepositories<TDbContext>(this IServiceCollection services, Assembly[] assemblies)
        {
            RegisterForGenericRepository<TDbContext>(services, assemblies);
            RegisterForCustomRepository(services, assemblies);

            return services;
        }

        private static void RegisterForGenericRepository<TDbContext>(IServiceCollection services, Assembly[] assemblies)
        {
            var entityTypes = assemblies
                .SelectMany(s => s.GetTypes())
                .Where(t => typeof(IEntity).IsAssignableFrom(t) && t != typeof(IEntity) && !t.IsGenericType)
                .ToArray();

            foreach (var entityType in entityTypes)
            {
                var primaryKeyType = FindPrimaryKeyType(entityType);
                var repositoryType = typeof(IRepository<,>).MakeGenericType(entityType, primaryKeyType);
                var efRepositoryType = typeof(EfCoreRepositoryBase<,,>).MakeGenericType(typeof(TDbContext), entityType, primaryKeyType);
                services.AddTransient(repositoryType, efRepositoryType);
            }
        }

        private static void RegisterForCustomRepository(IServiceCollection services, Assembly[] assemblies)
        {
            var interfaceTypes = assemblies
                .SelectMany(s => s.GetTypes())
                .Where(t => typeof(IRepository).IsAssignableFrom(t) && t != typeof(IRepository) && t.IsInterface && !t.IsGenericType)
                .ToArray();

            var implementTypes = assemblies
                .SelectMany(s => s.GetTypes())
                .Where(t => typeof(IRepository).IsAssignableFrom(t) && !t.IsInterface && !t.IsGenericType)
                .ToArray();

            foreach (var interfaceType in interfaceTypes)
            {
                var implementType = implementTypes.FirstOrDefault(interfaceType.IsAssignableFrom);
                if (implementType == null)
                {
                    continue;
                }

                services.AddTransient(interfaceType, implementType);
            }
        }

        private static Type FindPrimaryKeyType([NotNull] Type entityType)
        {
            if (!typeof(IEntity).IsAssignableFrom(entityType))
            {
                throw new Exception($"Given {nameof(entityType)} is not an entity. It should implement {typeof(IEntity).AssemblyQualifiedName}!");
            }

            foreach (var interfaceType in entityType.GetTypeInfo().GetInterfaces())
            {
                if (interfaceType.GetTypeInfo().IsGenericType &&
                    interfaceType.GetGenericTypeDefinition() == typeof(IEntity<>))
                {
                    return interfaceType.GenericTypeArguments[0];
                }
            }

            return null;
        }
    }
}
